﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  [DebuggerDisplay( "Count = {Count}" )]
  internal sealed class ListDataItemCollection : IList<RawDataItem>, ICollection<RawDataItem>, ICollection, INotifyCollectionChanged
  {
    #region Constructor

    internal ListDataItemCollection( DataSourceCollection dataSource )
    {
      if( dataSource == null )
        throw new ArgumentNullException( "dataSource" );

      m_sourceCollection = dataSource;
      m_sourceCollection.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnSourceCollectionChanged );
      m_dataSource = new List<ListDataItem>( m_sourceCollection.Count );
      m_readOnlyDataSource = new ReadOnlyCollection<ListDataItem>( m_dataSource );

      // Wraps the data objects.
      this.WrapNewUserObject();
      this.UpdatePrimaryKeys();
    }

    #endregion

    #region Count Property

    public int Count
    {
      get
      {
        lock( m_syncRoot )
        {
          return m_dataSource.Count;
        }
      }
    }

    #endregion

    #region Item Property

    public RawDataItem this[ int index ]
    {
      get
      {
        lock( m_syncRoot )
        {
          if( index < 0 || index >= m_dataSource.Count )
            throw new ArgumentOutOfRangeException( "index", "index must be greater than or equal to zero and less than the count of items in the data source." );

          return m_dataSource[ index ];
        }
      }
      set
      {
        throw new InvalidOperationException( "An attempt was made to add/insert an element into a read-only collection." );
      }
    }

    #endregion

    #region ElementType Property

    internal Type ElementType
    {
      get
      {
        return m_sourceCollection.ElementType;
      }
    }

    #endregion

    #region PrimaryKeyInfo Property

    internal ListDataItemPrimaryKeyDescription PrimaryKeyInfo
    {
      get
      {
        lock( m_syncRoot )
        {
          if( !m_primaryKeyInfo.HasValue )
          {
            m_primaryKeyInfo = new ListDataItemPrimaryKeyDescription(
                                 new SortDescription( ListDataItem.PrimaryKeyName, ListSortDirection.Ascending ),
                                 typeof( FakePrimaryKey ) );
          }

          return m_primaryKeyInfo.Value;
        }
      }
    }

    private Nullable<ListDataItemPrimaryKeyDescription> m_primaryKeyInfo; //null

    #endregion

    #region CollectionChanged Event

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    private void OnCollectionChanged( NotifyCollectionChangedEventArgs e )
    {
      NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;

      if( handlers != null )
      {
        handlers.Invoke( this, e );
      }
    }

    #endregion

    #region ItemChanged Event

    public event EventHandler<ItemChangedEventArgs> ItemChanged;

    private void OnItemChanged( ItemChangedEventArgs e )
    {
      EventHandler<ItemChangedEventArgs> handlers = this.ItemChanged;

      if( handlers != null )
      {
        handlers.Invoke( this, e );
      }
    }

    #endregion

    public int IndexOf( RawDataItem dataItem )
    {
      // Make sure the data item was created by this collection.
      ListDataItem item = dataItem as ListDataItem;
      if( !this.IsCreator( item ) )
        return -1;

      lock( m_syncRoot )
      {
        return item.PrimaryKey.Index;
      }
    }

    internal void Reset()
    {
      IEnumerable<KeyValuePair<int, ListDataItem>> itemsAdded;
      IEnumerable<KeyValuePair<int, ListDataItem>> itemsRemoved;

      lock( m_syncRoot )
      {
        this.SynchronizeWithDataSource( out itemsAdded, out itemsRemoved );
      }

      // Notify of the changes.
      NotifyCollectionChangedEventArgs eventArgs = new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset );
      this.OnCollectionChanged( eventArgs );
    }

    internal void Synchronize()
    {
      IEnumerable<KeyValuePair<int, ListDataItem>> itemsAdded;
      IEnumerable<KeyValuePair<int, ListDataItem>> itemsRemoved;

      lock( m_syncRoot )
      {
        this.SynchronizeWithDataSource( out itemsAdded, out itemsRemoved );
      }

      // Notify the listeners of the items removed.
      foreach( KeyValuePair<int, ListDataItem> itemRemoved in itemsRemoved )
      {
        NotifyCollectionChangedEventArgs eventArgs = new NotifyCollectionChangedEventArgs(
                                                       NotifyCollectionChangedAction.Remove,
                                                       itemRemoved.Value,
                                                       itemRemoved.Key );
        this.OnCollectionChanged( eventArgs );
      }

      // Notify the listeners of the items added.
      foreach( KeyValuePair<int, ListDataItem> itemAdded in itemsAdded )
      {
        NotifyCollectionChangedEventArgs eventArgs = new NotifyCollectionChangedEventArgs(
                                                       NotifyCollectionChangedAction.Add,
                                                       itemAdded.Value,
                                                       itemAdded.Key );
        this.OnCollectionChanged( eventArgs );
      }
    }

    private bool IsCreator( ListDataItem dataItem )
    {
      return ( dataItem != null && object.ReferenceEquals( dataItem.Source, this ) );
    }

    private IList<KeyValuePair<int, ListDataItem>> WrapNewUserObject()
    {
      ICollection sourceCollection = m_sourceCollection;

      lock( sourceCollection.SyncRoot )
      {
        // Create a list of data items that is a one to one match with
        // the user's collection.
        if( m_dataSource.Count <= 0 )
        {
          int itemsCount = sourceCollection.Count;
          while( itemsCount > 0 )
          {
            m_dataSource.Add( null );
            itemsCount--;
          }
        }

        // Replace the blanks in the data source with a new data item
        // that wraps the corresponding user object.
        List<KeyValuePair<int, ListDataItem>> newDataItems = new List<KeyValuePair<int, ListDataItem>>();
        int index = 0;

        foreach( object dataObject in sourceCollection )
        {
          ListDataItem dataItem = m_dataSource[ index ];

          // This is a new user object.
          if( dataItem == null )
          {
            FakePrimaryKey primaryKey = new FakePrimaryKey( m_readOnlyDataSource );
            primaryKey.Index = index;

            // Wrap the user object.
            dataItem = new ListDataItem( m_readOnlyDataSource, primaryKey, dataObject );
            m_dataSource[ index ] = dataItem;

            // This data item is a new data item.
            newDataItems.Add( new KeyValuePair<int, ListDataItem>( index, dataItem ) );

            // Listen to the notification event from the data object.
            this.RegisterItem( dataObject );
          }
          // We must keep the index value in sync with the real data item
          // position if we want to be able to retrieve it from the primary
          // key object.
          else
          {
            dataItem.PrimaryKey.Index = index;
          }

          index++;
        }

        return newDataItems;
      }
    }

    private void UpdatePrimaryKeys()
    {
      if( m_dataSource.Count <= 0 )
        return;

      Func<WeakReference, FakePrimaryKey> selectPrimaryKey = ( WeakReference item ) => item.Target as FakePrimaryKey;

      // Order the WeakReference by the FakePrimaryKey it contains.  The garbage collected
      // primary keys are placed at the end of the list to make them easier to remove.
      m_removedPrimaryKeys = m_removedPrimaryKeys.OrderBy(
                              selectPrimaryKey,
                              new FakePrimaryKeyComparer() ).ToList();

      // Remove every primary key collected by the garbage collector.
      while( true )
      {
        int weakReferenceIndex = m_removedPrimaryKeys.Count - 1;

        if( weakReferenceIndex < 0 )
          break;

        if( m_removedPrimaryKeys[ weakReferenceIndex ].IsAlive )
          break;

        m_removedPrimaryKeys.RemoveAt( weakReferenceIndex );
      }

      // Update the internal value of every primary key alive to keep
      // a right primary key comparison.
      int dataSourceIndex = 0;
      int uniqueValue = 0;
      int uniqueValueOffset = 0;
      IEnumerator<WeakReference> removedPrimaryKeys = m_removedPrimaryKeys.GetEnumerator();
      FakePrimaryKey currentRemovedPrimaryKey = null;

      while( true )
      {
        // Get the next removed primary key that must be updated.
        FakePrimaryKey currentPrimaryKey = null;
        while( removedPrimaryKeys != null && currentRemovedPrimaryKey == null )
        {
          if( removedPrimaryKeys.MoveNext() )
          {
            currentRemovedPrimaryKey = removedPrimaryKeys.Current.Target as FakePrimaryKey;
          }
          // There is no removed primary key left.
          else
          {
            removedPrimaryKeys = null;
          }
        }

        // We have updated every primary key.
        if( currentRemovedPrimaryKey == null && dataSourceIndex >= m_dataSource.Count )
          break;

        // The remaining primary keys to update are in the wrapped list.
        if( currentRemovedPrimaryKey == null )
        {
          currentPrimaryKey = m_dataSource[ dataSourceIndex ].PrimaryKey;
          dataSourceIndex++;
        }
        // The remaining primary keys to update are in the remove list.
        else if( dataSourceIndex >= m_dataSource.Count )
        {
          currentPrimaryKey = currentRemovedPrimaryKey;
          currentRemovedPrimaryKey = null;
        }
        else
        {
          // The internal value of both the active and removed primary keys must
          // be unique to avoid collisions.  The removed primary key at index X is
          // updated before the active primary key at the same index to keep the 
          // ordering has close as possible to the original ordering.
          if( uniqueValue >= currentRemovedPrimaryKey.UniqueValue + uniqueValueOffset )
          {
            currentPrimaryKey = currentRemovedPrimaryKey;
            currentRemovedPrimaryKey = null;
          }
          else
          {
            currentPrimaryKey = m_dataSource[ dataSourceIndex ].PrimaryKey;
            dataSourceIndex++;
          }

          // A new primary key added here will slide the remaining removed primary keys.
          if( currentPrimaryKey.IsNew )
          {
            uniqueValueOffset++;
          }
        }

        currentPrimaryKey.UniqueValue = uniqueValue++;
      }
    }

    private void SynchronizeWithDataSource(
      out IEnumerable<KeyValuePair<int, ListDataItem>> itemsAdded,
      out IEnumerable<KeyValuePair<int, ListDataItem>> itemsRemoved )
    {
      ICollection sourceCollection = m_sourceCollection;

      lock( sourceCollection.SyncRoot )
      {
        List<ListDataItem> newDataSource = new List<ListDataItem>( m_dataSource.Count );

        foreach( object dataObject in sourceCollection )
        {
          ListDataItem dataItem = null;

          // Retrieve the index of the first DataItem that contains the user data object
          // and move it into a temporary list.
          int dataSourceItemsCount = m_dataSource.Count;
          for( int i = 0; i < dataSourceItemsCount; i++ )
          {
            if( m_dataSource[ i ].Data == dataObject )
            {
              dataItem = m_dataSource[ i ];

              newDataSource.Add( dataItem );
              m_dataSource.RemoveAt( i );
              break;
            }
          }

          // The data object wasn't found in the DataItem list, so it is a new user data object.
          // We'll insert a blank that will be filled later.
          if( dataItem == null )
          {
            newDataSource.Add( null );
          }
        }

        // The data items that haven't been move in the temporary list have been
        // removed in the original data source.
        itemsRemoved = ( from dataItem in m_dataSource
                         select new KeyValuePair<int, ListDataItem>( dataItem.PrimaryKey.Index, dataItem ) ).ToList();

        // Move from the temporary list into the data items collection.  The temporary list
        // contains the items in the same order as the original data source.  We aren't swapping
        // the list in case an enumerator is referencing the internal list.
        m_dataSource.Clear();
        m_dataSource.AddRange( newDataSource );
        newDataSource.Clear();
      }

      // Cleanup the data items that have been removed.
      foreach( KeyValuePair<int, ListDataItem> item in itemsRemoved )
      {
        ListDataItem dataItem = item.Value;

        // Mark the data item and it's primary key as deleted.
        m_removedPrimaryKeys.Add( new WeakReference( dataItem.PrimaryKey ) );
        dataItem.Delete();

        // Remove any event handler on the data object.
        this.UnregisterItem( dataItem.Data );
      }

      // Fill in the blanks that were inserted previously.
      itemsAdded = this.WrapNewUserObject();

      // Update the internal key of each data item for consistency.
      this.UpdatePrimaryKeys();
    }

    private void UpdateIndexes()
    {
      if( m_dataSource.Count <= 0 )
        return;

      for( int i = 0; i < m_dataSource.Count; i++ )
      {
        m_dataSource[ i ].PrimaryKey.Index = i;
      }
    }

    private bool TryGetItem( object dataObject, out ListDataItem dataItem )
    {
      dataItem = m_dataSource.FirstOrDefault( ( ListDataItem item ) => ( item.Data == dataObject ) );

      return ( dataItem != null );
    }

    private void RegisterItem( object item )
    {
      INotifyPropertyChanged observableItem = item as INotifyPropertyChanged;
      if( observableItem == null )
        return;

      observableItem.PropertyChanged += new PropertyChangedEventHandler( this.OnDataObjectPropertyChanged );
    }

    private void UnregisterItem( object item )
    {
      INotifyPropertyChanged observableItem = item as INotifyPropertyChanged;
      if( observableItem == null )
        return;

      observableItem.PropertyChanged -= new PropertyChangedEventHandler( this.OnDataObjectPropertyChanged );
    }

    private void ManageItemsAdded( NotifyCollectionChangedEventArgs e )
    {
      if( e.Action != NotifyCollectionChangedAction.Add || e.NewItems.Count <= 0 )
        return;

      // Insert some blanks where the data object will be inserted.
      int index = e.NewStartingIndex;
      int count = e.NewItems.Count;
      IList<KeyValuePair<int, ListDataItem>> newDataItems;
      bool raiseEvent;

      lock( m_syncRoot )
      {
        while( count > 0 )
        {
          m_dataSource.Insert( index, null );
          count--;
        }

        // Wraps the newly added data objects and synchronize the primary keys.
        newDataItems = this.WrapNewUserObject();
        raiseEvent = newDataItems.Any();

        if( raiseEvent )
        {
          this.UpdatePrimaryKeys();
        }
      }

      if( raiseEvent )
      {
        foreach( KeyValuePair<int, ListDataItem> newDataItem in newDataItems )
        {
          NotifyCollectionChangedEventArgs eventArgs = new NotifyCollectionChangedEventArgs(
                                                         NotifyCollectionChangedAction.Add,
                                                         newDataItem.Value,
                                                         newDataItem.Key );
          this.OnCollectionChanged( eventArgs );
        }
      }
    }

    private void ManageItemsRemoved( NotifyCollectionChangedEventArgs e )
    {
      if( e.Action != NotifyCollectionChangedAction.Remove || e.OldItems.Count <= 0 )
        return;

      // Find out which data items must be removed.
      int index = e.OldStartingIndex;
      int count = e.OldItems.Count;
      IList<KeyValuePair<int, ListDataItem>> oldDataItems = new List<KeyValuePair<int, ListDataItem>>();
      bool raiseEvent;

      lock( m_syncRoot )
      {
        while( count > 0 )
        {
          ListDataItem dataItem = m_dataSource[ index ];

          // Mark the data item and it's primary key as deleted.
          m_removedPrimaryKeys.Add( new WeakReference( dataItem.PrimaryKey ) );
          dataItem.Delete();

          // Remove any event handler on the data object.
          this.UnregisterItem( dataItem.Data );

          oldDataItems.Add( new KeyValuePair<int, ListDataItem>( index, dataItem ) );

          // Remove the data item from the collection.
          m_dataSource.RemoveAt( index );
          count--;
        }

        raiseEvent = oldDataItems.Any();

        if( raiseEvent )
        {
          this.UpdateIndexes();
        }
      }

      // Notify of the changes.
      if( raiseEvent )
      {
        foreach( KeyValuePair<int, ListDataItem> oldDataItem in oldDataItems )
        {
          NotifyCollectionChangedEventArgs eventArgs = new NotifyCollectionChangedEventArgs(
                                                         NotifyCollectionChangedAction.Remove,
                                                         oldDataItem.Value,
                                                         oldDataItem.Key );
          this.OnCollectionChanged( eventArgs );
        }
      }
    }

    private void ManageItemsReplaced( NotifyCollectionChangedEventArgs e )
    {
      if( e.Action != NotifyCollectionChangedAction.Replace )
        return;

      // Fill the list of old data items.
      IList<ListDataItem> oldDataItems = new List<ListDataItem>( e.OldItems.Count );
      IList<KeyValuePair<int, ListDataItem>> newDataItems;
      int startIndex = e.NewStartingIndex;
      int endIndex = startIndex + e.NewItems.Count;

      lock( m_syncRoot )
      {
        // Remove the old data items from the data source.
        for( int i = startIndex; i < endIndex; i++ )
        {
          ListDataItem dataItem = m_dataSource[ i ];

          oldDataItems.Add( dataItem );
          this.UnregisterItem( dataItem.Data );

          dataItem.Delete();
          m_removedPrimaryKeys.Add( new WeakReference( dataItem.PrimaryKey ) );

          m_dataSource[ i ] = null;
        }

        // Fill the list of new data items.
        newDataItems = this.WrapNewUserObject();

        if( newDataItems.Any() )
        {
          this.UpdatePrimaryKeys();
        }
      }

      // Notify of the changes.
      IEnumerable<Tuple<int, ListDataItem, ListDataItem>> replacedItems =
        newDataItems.Zip( oldDataItems, ( x, y ) => new Tuple<int, ListDataItem, ListDataItem>( x.Key, x.Value, y ) );
      foreach( Tuple<int, ListDataItem, ListDataItem> item in replacedItems )
      {
        NotifyCollectionChangedEventArgs eventArgs = new NotifyCollectionChangedEventArgs(
                                                       NotifyCollectionChangedAction.Replace,
                                                       item.Item2,
                                                       item.Item3,
                                                       item.Item1 );
        this.OnCollectionChanged( eventArgs );
      }
    }

    private void ManageItemChanged( ItemChangedDescription itemChanged )
    {
      if( itemChanged == null )
        return;

      // Retrieve the corresponding data item.
      ListDataItem dataItem;

      lock( m_syncRoot )
      {
        if( !this.TryGetItem( itemChanged.Item, out dataItem ) )
          return;
      }

      // No data item was found.
      if( dataItem == null )
        return;

      // Notify of the changes.
      ItemChangedDescription newItemChanged = itemChanged.CreateWithNewItem( dataItem );
      ItemChangedEventArgs eventArgs = new ItemChangedEventArgs( new ItemChangedDescription[] { newItemChanged } );

      this.OnItemChanged( eventArgs );
    }

    private void ManageItemsReset( NotifyCollectionChangedEventArgs e )
    {
      if( e.Action != NotifyCollectionChangedAction.Reset )
        return;

      this.Reset();
    }

    private void OnSourceCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      if( e.Action == NotifyCollectionChangedAction.Add )
      {
        if( e.NewItems.Count > 0 )
        {
          this.ManageItemsAdded( e );
        }
      }
      else if( e.Action == NotifyCollectionChangedAction.Remove )
      {
        if( e.OldItems.Count > 0 )
        {
          this.ManageItemsRemoved( e );
        }
      }
      else if( e.Action == NotifyCollectionChangedAction.Replace )
      {
        if( ( e.NewItems.Count > 0 ) && ( e.NewItems.Count == e.OldItems.Count ) )
        {
          this.ManageItemsReplaced( e );
        }
      }
      else if( e.Action == NotifyCollectionChangedAction.Reset )
      {
        this.ManageItemsReset( e );
      }
      else
      {
        throw new InvalidOperationException( "The received collection-changed action is not supported." );
      }
    }

    private void OnDataObjectPropertyChanged( object sender, PropertyChangedEventArgs e )
    {
      this.ManageItemChanged( new ItemChangedDescription( sender, e.PropertyName ) );
    }

    #region IList<RawDataItem> Members

    void IList<RawDataItem>.Insert( int index, RawDataItem item )
    {
      throw new NotSupportedException( "Cannot insert an element into a read-only collection." );
    }

    void IList<RawDataItem>.RemoveAt( int index )
    {
      throw new NotSupportedException( "Cannot remove an element from a read-only collection." );
    }

    #endregion

    #region ICollection<RawDataItem> Members

    bool ICollection<RawDataItem>.IsReadOnly
    {
      get
      {
        return true;
      }
    }

    bool ICollection<RawDataItem>.Contains( RawDataItem item )
    {
      ListDataItem dataItem = item as ListDataItem;
      if( dataItem == null )
        return false;

      lock( m_syncRoot )
      {
        return m_dataSource.Contains( dataItem );
      }
    }

    void ICollection<RawDataItem>.CopyTo( RawDataItem[] array, int arrayIndex )
    {
      RawDataItem[] sourceArray;

      lock( m_syncRoot )
      {
        sourceArray = m_dataSource.ToArray();
      }

      Array.Copy( sourceArray, 0, array, arrayIndex, sourceArray.Length );
    }

    void ICollection<RawDataItem>.Clear()
    {
      throw new NotSupportedException( "Cannot clear a read-only collection." );
    }

    void ICollection<RawDataItem>.Add( RawDataItem item )
    {
      throw new NotSupportedException( "Cannot add an element to a read-only collection." );
    }

    bool ICollection<RawDataItem>.Remove( RawDataItem item )
    {
      throw new NotSupportedException( "Cannot remove an element from a read-only collection." );
    }

    #endregion

    #region ICollection Members

    public object SyncRoot
    {
      get
      {
        return m_syncRoot;
      }
    }

    private readonly object m_syncRoot = new object();

    bool ICollection.IsSynchronized
    {
      get
      {
        return true;
      }
    }

    void ICollection.CopyTo( Array array, int index )
    {
      RawDataItem[] sourceArray;

      lock( m_syncRoot )
      {
        sourceArray = m_dataSource.ToArray();
      }

      Array.Copy( sourceArray, 0, array, index, sourceArray.Length );
    }

    #endregion

    #region IEnumerable<RawDataItem> Members

    public IEnumerator<RawDataItem> GetEnumerator()
    {
      lock( m_syncRoot )
      {
        return m_dataSource.Cast<RawDataItem>().GetEnumerator();
      }
    }

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
      lock( m_syncRoot )
      {
        return m_dataSource.GetEnumerator();
      }
    }

    #endregion

    #region Private Fields

    private readonly DataSourceCollection m_sourceCollection;
    private readonly List<ListDataItem> m_dataSource;
    private readonly ReadOnlyCollection<ListDataItem> m_readOnlyDataSource;
    private IList<WeakReference> m_removedPrimaryKeys = new List<WeakReference>();

    #endregion

    #region Private FakePrimaryKeyComparer Nested Type

    private class FakePrimaryKeyComparer : IComparer<FakePrimaryKey>
    {
      internal FakePrimaryKeyComparer()
      {
      }

      int IComparer<FakePrimaryKey>.Compare( FakePrimaryKey x, FakePrimaryKey y )
      {
        if( x == null && y == null )
        {
          return 0;
        }
        else if( x == null )
        {
          return 1;
        }
        else if( y == null )
        {
          return -1;
        }
        else
        {
          return x.CompareTo( y );
        }
      }
    }

    #endregion

    #region Private FakePrimaryKey Nested Type

    [DebuggerDisplay( "UniqueValue = {UniqueValue}, Index = {Index}" )]
    private class FakePrimaryKey : IComparable<FakePrimaryKey>, IComparable
    {
      internal const int NewKey = int.MinValue;
      private const int InvalidIndex = -1;


      internal FakePrimaryKey( ReadOnlyCollection<ListDataItem> dataList )
      {
        if( dataList == null )
          throw new ArgumentNullException( "dataList" );

        this.UniqueValue = FakePrimaryKey.NewKey;
        this.Index = FakePrimaryKey.InvalidIndex;
        this.Source = dataList;
      }


      internal int UniqueValue
      {
        get;
        set;
      }

      internal int Index
      {
        get;
        set;
      }

      internal IList<ListDataItem> Source
      {
        get;
        private set;
      }

      internal bool IsNew
      {
        get
        {
          return ( this.UniqueValue == FakePrimaryKey.NewKey );
        }
      }

      internal bool IsDeleted
      {
        get;
        private set;
      }


      public int CompareTo( FakePrimaryKey key )
      {
        if( key == null || !object.ReferenceEquals( key.Source, this.Source ) )
          throw new DataInternalException( "Cannot compare keys from different sources." );

        return this.UniqueValue.CompareTo( key.UniqueValue );
      }

      public int CompareTo( object key )
      {
        return this.CompareTo( key as FakePrimaryKey );
      }

      internal void Delete()
      {
        this.IsDeleted = true;
        this.Index = FakePrimaryKey.InvalidIndex;
      }
    }

    #endregion

    #region Private ListDataItem Nested Type

    [DebuggerDisplay( "Data = {Data}" )]
    private class ListDataItem : RawDataItem
    {
      // This property name is used to identify a unique item.
      // It contains invalid property name symbols to make sure it
      // won't collid with an existing property name on the user's
      // data object.
      internal const string PrimaryKeyName = "#@@_PRIMARY_KEY_@@#";

      internal ListDataItem(
        ReadOnlyCollection<ListDataItem> dataList,
        FakePrimaryKey key,
        object dataObject )
        : base( dataObject )
      {
        if( dataList == null )
          throw new ArgumentNullException( "dataList" );

        if( key == null )
          throw new ArgumentNullException( "key" );

        this.PrimaryKey = key;
        this.Source = dataList;
      }

      internal FakePrimaryKey PrimaryKey
      {
        get;
        private set;
      }

      internal IList<ListDataItem> Source
      {
        get;
        private set;
      }

      public override int GetHashCode()
      {
        return this.PrimaryKey.GetHashCode();
      }

      public override bool Equals( object obj )
      {
        if( object.ReferenceEquals( obj, null ) )
          return false;

        if( object.ReferenceEquals( obj, this ) )
          return true;

        ListDataItem dataItem = obj as ListDataItem;

        if( object.ReferenceEquals( dataItem, null ) )
          return false;

        return object.Equals( dataItem.PrimaryKey, this.PrimaryKey );
      }

      protected override bool FindDataMemberValue( string propertyName, out object value )
      {
        if( propertyName == ListDataItem.PrimaryKeyName )
        {
          value = this.PrimaryKey;
          return true;
        }

        return base.FindDataMemberValue( propertyName, out value );
      }

      internal override void Delete()
      {
        base.Delete();
        this.PrimaryKey.Delete();
      }
    }

    #endregion
  }
}
